package com.chenjim.glrecorder;

import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.ImageFormat;
import android.graphics.SurfaceTexture;
import android.hardware.Camera;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraManager;
import android.hardware.camera2.CameraMetadata;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.params.OutputConfiguration;
import android.hardware.camera2.params.SessionConfiguration;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.media.MediaCodec;
import android.os.Handler;
import android.os.HandlerThread;
import android.util.Log;
import android.util.Size;
import android.view.Surface;

import androidx.annotation.NonNull;
import androidx.core.app.ActivityCompat;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.locks.ReentrantLock;

public class CameraHelper implements Camera.PreviewCallback {

    private static final String TAG = "CameraHelper";
    public static final int WIDTH = 1920;
    public static final int HEIGHT = 1080;
    private int mCameraId;
    private Camera mCamera;
    private byte[] buffer;
    private Camera.PreviewCallback mPreviewCallback;
    private SurfaceTexture mSurfaceTexture;
    private Surface mPreviewSurface;
    private CameraManager manager;
    private Context mActivity;


    public CameraHelper(int cameraId) {
        mCameraId = cameraId;
    }

    public CameraHelper(Context activity) {
        this.mActivity = activity;
        manager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE);
    }

    public void switchCamera() {
        if (mCameraId == Camera.CameraInfo.CAMERA_FACING_BACK) {
            mCameraId = Camera.CameraInfo.CAMERA_FACING_FRONT;
        } else {
            mCameraId = Camera.CameraInfo.CAMERA_FACING_BACK;
        }
        stopPreview();
        startPreview(mSurfaceTexture);
    }

    public int getCameraId() {
        return mCameraId;
    }

    public void stopPreview() {
        if (mCamera != null) {
            //预览数据回调接口
            mCamera.setPreviewCallback(null);
            //停止预览
            mCamera.stopPreview();
            //释放摄像头
            mCamera.release();
            mCamera = null;
        }
    }

    public void startPreview(SurfaceTexture surfaceTexture) {
        mSurfaceTexture = surfaceTexture;
        try {
            //获得camera对象
            mCamera = Camera.open(mCameraId);
            //配置camera的属性
            Camera.Parameters parameters = mCamera.getParameters();
            //设置预览数据格式为nv21
            parameters.setPreviewFormat(ImageFormat.NV21);
            //这是摄像头宽、高
            parameters.setPreviewSize(WIDTH, HEIGHT);

            List<int[]> list = parameters.getSupportedPreviewFpsRange();
            Log.d(TAG, "SupportedPreviewFpsRange:" + list);
            int[] previewFps = list.get(list.size() - 1);
            parameters.setPreviewFpsRange(previewFps[0], previewFps[1]);

            // 设置摄像头 图像传感器的角度、方向
            mCamera.setParameters(parameters);
            buffer = new byte[WIDTH * HEIGHT * 3 / 2];
            //数据缓存区
            mCamera.addCallbackBuffer(buffer);
            mCamera.setPreviewCallbackWithBuffer(this);
            //设置预览画面
            mCamera.setPreviewTexture(mSurfaceTexture);

            mCamera.startPreview();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }


    public void setPreviewCallback(Camera.PreviewCallback previewCallback) {
        mPreviewCallback = previewCallback;
    }


    @Override
    public void onPreviewFrame(byte[] data, Camera camera) {
        //SurfaceView 直接预览的  会影响到你的预览吗？
        //Thread.sleep(10_000);
        // data数据依然是倒的
        if (null != mPreviewCallback) {
            mPreviewCallback.onPreviewFrame(data, camera);
        }
        camera.addCallbackBuffer(buffer);
    }


    private final int CAMERA_STATE_OPENING = 0x001;
    private final int CAMERA_STATE_OPENED = 0x002;
    private final int CAMERA_STATE_CLOSE = 0x003;
    private final int CAMERA_STATE_ERROR = 0x004;
    private final int CAMERA_STATE_PREVIEW = 0x005;

    private CameraCharacteristics mCameraCharacteristics;
    private CameraDevice mCameraDevice;
    private CameraCaptureSession mCameraSession;
    private CaptureRequest.Builder mRequestBuilder;
    private Handler mCameraHandler;
    private HandlerThread mCameraThread;
    private final ReentrantLock mCameraStateLock = new ReentrantLock();
    private int mCameraState = CAMERA_STATE_CLOSE;

    // Clockwise angle through which the output image needs to be rotated to be upright on the device screen.
    // Range of valid values: 0, 90, 180, 270
    // for set video orientation
    private int mDisplayRotation;
    private final Size mPreviewSize = new Size(1280, 720);

    public void setPreviewSurface(SurfaceTexture surfaceTexture) {
        surfaceTexture.setDefaultBufferSize(mPreviewSize.getWidth() * 2 , mPreviewSize.getHeight() * 2);
        mPreviewSurface = new Surface(surfaceTexture);
    }

    public void openCamera() {
        if (mCameraState != CAMERA_STATE_CLOSE) {
            Log.e(TAG, "only could open camera when closed");
            return;
        }
        if (ActivityCompat.checkSelfPermission(mActivity, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
            Log.e(TAG, "Open camera failed! No permission CAMERA.");
            return;
        }
        if (mPreviewSurface == null) {
            Log.e(TAG, "Open camera failed! No PreviewSurface");
            return;
        }
        mCameraStateLock.lock();
        String mCameraId = null;
        try {
            for (String cameraId : manager.getCameraIdList()) {
                CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId);
                // 默认打开后置摄像头 - 忽略前置摄像头
                if (characteristics.get(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_FRONT)
                    continue;
                // 获取StreamConfigurationMap，它是管理摄像头支持的所有输出格式和尺寸
                StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
                mCameraId = cameraId;
                break;
            }
        } catch (CameraAccessException e) {
            throw new RuntimeException(e);
        }
        Log.i(TAG, "openCamera --> cameraId: " + mCameraId);
        mCameraState = CAMERA_STATE_OPENING;
        startBackgroundThread();
        try {
            String[] cameraIdList = manager.getCameraIdList();
            boolean isValidCameraId = false;
            for (String s : cameraIdList) {
                if (s.equals(mCameraId)) {
                    isValidCameraId = true;
                    break;
                }
            }
            if (isValidCameraId) {
                mCameraCharacteristics = manager.getCameraCharacteristics(mCameraId);
                initDisplayRotation(mCameraCharacteristics);
                manager.openCamera(mCameraId, mCameraOpenCallback, mCameraHandler);
            } else {
                Log.e(TAG, "openCamera failed! invalid camera id: " + mCameraId);
            }
        } catch (CameraAccessException e) {
            e.printStackTrace();
        } finally {
            mCameraStateLock.unlock();
        }
    }

    private final CameraDevice.StateCallback mCameraOpenCallback = new CameraDevice.StateCallback() {
        @Override
        public void onOpened(@NonNull CameraDevice camera) {
            mCameraState = CAMERA_STATE_OPENED;
            mCameraDevice = camera;
            createVideoSession();  // create session after open camera
        }

        @Override
        public void onDisconnected(@NonNull CameraDevice camera) {
            mCameraState = CAMERA_STATE_ERROR;
            releaseCamera();
        }

        @Override
        public void onError(@NonNull CameraDevice camera, int error) {
            Log.e(TAG, "Camera onError: " + error);
            mCameraState = CAMERA_STATE_ERROR;
            releaseCamera();
        }
    };

    private void startBackgroundThread() {
        if (mCameraThread == null || mCameraHandler == null) {
            Log.v(TAG, "startBackgroundThread");
            mCameraThread = new HandlerThread("CameraBackground");
            mCameraThread.start();
            mCameraHandler = new Handler(mCameraThread.getLooper());
        }
    }

    private void initDisplayRotation(CameraCharacteristics cameraCharacteristics) {
        if (cameraCharacteristics == null || mActivity == null) {
            return;
        }
        /*int displayRotation = mActivity.getWindowManager().getDefaultDisplay().getRotation();
        switch (displayRotation) {
            case Surface.ROTATION_0:
                displayRotation = 90;
                break;
            case Surface.ROTATION_90:
                displayRotation = 0;
                break;
            case Surface.ROTATION_180:
                displayRotation = 270;
                break;
            case Surface.ROTATION_270:
                displayRotation = 180;
                break;
        }
        int sensorOrientation = cameraCharacteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
        mDisplayRotation = 180;
        Log.d(TAG, "mDisplayRotation: " + mDisplayRotation);*/
    }

    private void createVideoSession() {
        Log.v(TAG, "createVideoSession start...");
        try {
            // video surface
            ArrayList<Surface> sessionSurfaces = new ArrayList<>();
            sessionSurfaces.add(mPreviewSurface);
            createPreviewRequest();
            mCameraDevice.createCaptureSession(sessionSurfaces, mSessionCreateCallback, mCameraHandler);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

    private void createPreviewRequest() {
        CaptureRequest.Builder builder;
        try {
            builder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
        } catch (CameraAccessException e) {
            e.printStackTrace();
            Log.e(TAG, "setUpPreviewRequest, Camera access failed");
            return;
        }
        builder.addTarget(mPreviewSurface);
        applyCommonSettings(builder);
        mRequestBuilder = builder;
    }

    private void applyCommonSettings(CaptureRequest.Builder builder) {
        if (builder == null) {
            return;
        }
        builder.set(CaptureRequest.CONTROL_MODE, CaptureRequest.CONTROL_MODE_AUTO);
        builder.set(CaptureRequest.CONTROL_AF_MODE, CameraMetadata.CONTROL_AF_MODE_CONTINUOUS_VIDEO);  // set auto focus mode
        builder.set(CaptureRequest.CONTROL_AWB_MODE, CaptureRequest.CONTROL_AWB_MODE_AUTO); // set auto white balance mode
        builder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH); // set auto exposure mode
    }

    private final CameraCaptureSession.StateCallback mSessionCreateCallback = new CameraCaptureSession.StateCallback() {
        @Override
        public void onConfigured(@NonNull CameraCaptureSession session) {
            mCameraSession = session;
            startPreview();
        }

        @Override
        public void onConfigureFailed(@NonNull CameraCaptureSession session) {
            releaseCamera();
        }
    };

    public void startPreview() {
        Log.v(TAG, "startPreview");
        if (mCameraSession == null || mRequestBuilder == null) {
            Log.w(TAG, "startPreview failed. mCaptureSession or mCurrentRequestBuilder is null");
            return;
        }
        try {
            CaptureRequest captureRequest = mRequestBuilder.build();
            mCameraSession.setRepeatingRequest(captureRequest, null, mCameraHandler);
            mCameraState = CAMERA_STATE_PREVIEW;
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

    public void releaseCamera() {
        if (mCameraState == CAMERA_STATE_CLOSE) {
            Log.w(TAG, "camera is closed");
            return;
        }
        mCameraStateLock.lock();
        Log.v(TAG, "releaseCamera");
        stopPreviewS();
        closeCameraSession();
        closeCameraDevice();
        stopBackgroundThread();
        mCameraState = CAMERA_STATE_CLOSE;
        mCameraStateLock.unlock();
    }

    public void stopPreviewS() {
        Log.v(TAG, "stopPreview");
        if (mCameraSession == null) {
            Log.w(TAG, "stopPreview: mCaptureSession is null");
            return;
        }
        try {
            mCameraSession.stopRepeating();
            mCameraState = CAMERA_STATE_OPENED;
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

    private void closeCameraSession() {
        if (mCameraSession != null) {
            mCameraSession.close();
            mCameraSession = null;
        }
    }

    private void closeCameraDevice() {
        if (mCameraDevice != null) {
            mCameraDevice.close();
            mCameraDevice = null;
        }
    }

    private void stopBackgroundThread() {
        Log.v(TAG, "stopBackgroundThread");
        if (mCameraThread != null) {
            mCameraThread.quitSafely();
            try {
                mCameraThread.join();
                mCameraThread = null;
                mCameraHandler = null;
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
